/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package org.xxforest.game.core.scenes.scene2d;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import org.xxforest.game.core.XX;
import org.xxforest.game.core.common.XXDisposable;
import org.xxforest.game.core.scenes.scene2d.events.XXEvent;
import org.xxforest.game.core.scenes.scene2d.events.XXEventListener;
import org.xxforest.game.core.scenes.scene2d.events.XXEventType;
import org.xxforest.game.core.scenes.scene2d.events.listeners.XXFocusListener;
import org.xxforest.game.core.scenes.scene2d.events.listeners.XXInputListener;
import org.xxforest.game.core.scenes.scene2d.events.listeners.XXKeyPressListener;
import org.xxforest.game.core.scenes.scene2d.events.listeners.XXTouchDragListener;
import org.xxforest.game.core.scenes.scene2d.events.listeners.XXTouchListener;
import org.xxforest.game.core.tween.XXBaseTween;
import org.xxforest.game.core.tween.XXTween;
import org.xxforest.game.core.utils.XXPoint;

import aurelienribon.tweenengine.BaseTween;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector2;

public abstract class XXNode implements XXDisposable{
	
	private XXScreen screen;
	private XXNode parent;
	private LinkedList<XXBaseTween<?>> tweens = new LinkedList<XXBaseTween<?>>();
	private LinkedList<XXNode> children = new LinkedList<XXNode>() ;
	private XXEventListener<?>[] listeners = new XXEventListener[XXEventType.values().length];

	protected String name;
	private XXTouchable touchable = XXTouchable.enabled ;
	private boolean visible = true;
	private float width, height;
	private XXPoint point = XXPoint.zero();
	private XXPoint anchorPoint = XXPoint.zero();
	private float scaleX = 1, scaleY = 1;
	private float rotation;
	private final Color color = new Color(1, 1, 1, 1);
	private boolean created ;
	
	protected XXNode(){}
	
	public void render (SpriteBatch batch ,float delta) {
		draw(batch,delta);
		for(XXNode node : children){
			node.render(batch,delta);
		}
	}
	
	public abstract void draw(SpriteBatch batch , float delta);

	public abstract void onCreate();
	
	public void add(XXNode node){
		node.parent = this ;
		node.screen = screen ;
		this.children.add(node);
		for(XXNode ac : node.children){
			ac.screen = screen ;
		}
		if(!node.isCreated()){
			node.onCreate() ;
			node.setCreated(true);
		}
	}
	
	public void add(XXNode node , int order){
		node.parent = this ;
		node.screen = screen ;
		this.children.add(order,node);
		for(XXNode ac : node.children){
			ac.screen = screen ;
		}
		if(!node.isCreated()){
			node.onCreate() ;
			node.setCreated(true);
		}
	}
	
	public float getDrawAlpha(){
		if(parent != null){
			return parent.getDrawAlpha() + color.a ;
		}
		return color.a ;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	public boolean fire (XXEvent event) {
		XXEventListener eventListener = listeners[event.getEventType().ordinal()];
		if(eventListener != null){
			return eventListener.handle(event) ;
		}
		return false ;
	}
	
	public boolean remove(XXNode node){
		node.parent = null ;
		node.screen = null ;
		return this.children.remove(node);
	}

	public float getDrawX(){
		if(parent != null){
			return parent.getDrawX() + getX();
		}
		return getX();
	}
	
	public float getDrawY(){
		if(parent != null){
			return parent.getDrawY() + getY();
		}
		return getY() ;
	}
	
	public XXPoint getPoint(){
		return this.point ;
	}
	
	public XXPoint getAnchorPoint(){
		return this.anchorPoint ;
	}
	
	public void setPoint(XXPoint point){
		this.point = point ;
	}
	
	public void setAnchorPoint(XXPoint anchorPoint){
		this.anchorPoint = anchorPoint ;
	}
	
	public XXNode findHiter (float x, float y) {
		if(isTouchAble()){
			XXNode hiter = null ;
			for(XXNode node : children){
				hiter = node.findHiter(x - getParentX() , y - getParentY());
				if(hiter != null){
					return hiter ;
				}
			}
			if(hit(x, y) && touchable == XXTouchable.enabled){
				return this ;
			}
		}
		return null ;
	}

	private boolean hit(float x, float y) {
		float drawScaleX = getDrawScaleX();
		float drawScaleY = getDrawScaleY();
		float drawWidth = width * drawScaleX ;
		float drawHeight = height * drawScaleY ;
		float drawAnchorX = getAnchorPointX() * drawScaleX;
		float drawAnchorY = getAnchorPointY() * drawScaleY;
		
		float drawRotation = getDrawRotation();
		float offSetX =  drawAnchorX;
		float offSetY =  drawAnchorY;
		float tmpWidth = drawWidth;
		float tmpHeight = drawHeight;
		if(drawRotation != 0){
			if(Math.abs(drawRotation) > 90){
				drawRotation = 180 - Math.abs(drawRotation) ;
			}
			drawRotation = Math.abs(drawRotation);
			float cos = MathUtils.cosDeg(drawRotation);
			float sin = MathUtils.sinDeg(drawRotation);
			
			offSetX =  Math.abs(cos * drawAnchorX + sin * drawAnchorY);
			offSetY =  Math.abs(sin * drawAnchorX + cos * drawAnchorY);
			
			tmpWidth = Math.abs(cos *  drawWidth + sin * drawHeight);
			tmpHeight = Math.abs(sin *  drawWidth + cos * drawHeight);
			
		}
		float tmpx = x - getParentX() + offSetX ;
		float tmpy = y - getParentY() + offSetY ;
		
		if( tmpx >= point.x  && tmpx < tmpWidth + point.x  && tmpy >= point.y  && tmpy < tmpHeight + point.y ){
			return true ;
		}
		return false ;
	}
	
	public float getParentX(){
		if(parent == null){
			return 0;
		}
		return parent.getX();
	}
	
	public float getParentY(){
		if(parent == null){
			return 0;
		}
		return parent.getY();
	}

	public void setListener (XXEventType eventType , XXEventListener listener) {
		listeners[eventType.ordinal()] = listener ;
	}
	
	public void removeListener(XXEventType eventType){
		listeners[eventType.ordinal()] = null ;
	}

	public XXScreen getScreen () {
		return screen;
	}

	protected void setScreen (XXScreen screen) {
		this.screen = screen;
	}

	public boolean isDescendantOf (XXNode node) {
		if (node == null) throw new IllegalArgumentException("node cannot be null.");
		XXNode parent = this;
		while (true) {
			if (parent == null) return false;
			if (parent == node) return true;
			parent = parent.getParent();
		}
	}

	public boolean isAscendantOf (XXNode node) {
		if (node == null) throw new IllegalArgumentException("node cannot be null.");
		while (true) {
			if (node == null) return false;
			if (node == this) return true;
			node = node.getParent();
		}
	}

	public boolean hasParent () {
		return parent != null;
	}

	public XXNode getParent () {
		return parent;
	}

	protected void setParent (XXNode parent) {
		this.parent = parent;
	}

	public XXTouchable getTouchable () {
		return touchable;
	}

	public void setTouchable (XXTouchable touchable) {
		this.touchable = touchable;
	}

	public boolean isVisible () {
		return visible;
	}

	public void setVisible (boolean visible) {
		this.visible = visible;
	}

	public float getX () {
		return point.x;
	}

	public void setX (float x) {
		point.x = x;
	}

	public float getY () {
		return point.y;
	}

	public void setY (float y) {
		point.y = y;
	}

	public void setPosition (float x, float y) {
		setX(x);
		setY(y);
	}

	public void translate (float x, float y) {
		setX(point.x + x);
		setY(point.y + y);
	}

	public float getWidth () {
		return width;
	}

	public void setWidth (float width) {
		this.width = width;
	}

	public float getHeight () {
		return height;
	}

	public void setHeight (float height) {
		this.height = height;
	}

	public float getTop () {
		return getY() + getHeight();
	}

	public float getRight () {
		return getX() + getWidth();
	}

	public void setSize (float width, float height) {
		setWidth(width);
		setHeight(height);
	}

	public void size (float size) {
		setWidth(width + size);
		setHeight(height + size);
	}

	public void size (float width, float height) {
		setWidth(this.width + width);
		setHeight(this.height + height);
	}

	public void setBounds (float x, float y, float width, float height) {
		setX(x);
		setY(y);
		setWidth(width);
		setHeight(height);
	}

	public float getAnchorPointX () {
		return anchorPoint.x;
	}

	public void setAnchorPointX (float anchorX) {
		anchorPoint.x = anchorX;
	}

	public float getAnchorPointY () {
		return anchorPoint.y;
	}

	public void setAnchorPointY (float anchorY) {
		anchorPoint.y = anchorY;
	}

	public void setAnchorPoint (float anchorX, float anchorY) {
		setAnchorPointX(anchorX);
		setAnchorPointY(anchorY);
	}

	public float getScaleX () {
		return scaleX;
	}
	
	public float getDrawScaleX(){
		if(parent != null){
			return parent.getDrawScaleX() * scaleX ;
		}
		return scaleX ;
	}
	
	public float getDrawScaleY(){
		if(parent != null){
			return parent.getDrawScaleY() * scaleY ;
		}
		return scaleY ;
	}

	public void setScaleX (float scaleX) {
		this.scaleX = scaleX;
	}

	public float getScaleY () {
		return scaleY;
	}

	public void setScaleY (float scaleY) {
		this.scaleY = scaleY;
	}

	public void setScale (float scale) {
		setScaleX(scale);
		setScaleY(scale);
	}

	public void setScale (float scaleX, float scaleY) {
		setScaleX(scaleX);
		setScaleY(scaleY);
	}

	public void scale (float scale) {
		setScaleX(scaleX + scale);
		setScaleY(scaleY + scale);
	}

	public void scale (float scaleX, float scaleY) {
		setScaleX(this.scaleX + scaleX);
		setScaleY(this.scaleY + scaleY);
	}

	public float getRotation () {
		return rotation;
	}

	public void setRotation (float degrees) {
		this.rotation = degrees;
	}

	public void rotate (float amountInDegrees) {
		setRotation(rotation + amountInDegrees);
	}

	public void setColor (Color color) {
		this.color.set(color);
	}

	public void setColor (float r, float g, float b, float a) {
		color.set(r, g, b, a);
	}

	public Color getColor () {
		return color;
	}

	public String getName () {
		return name;
	}

	public void setName (String name) {
		this.name = name;
	}

	public int getZIndex () {
		XXNode parent = getParent();
		if (parent == null) return -1;
		return parent.indexOf(this, true);
	}

	private int indexOf(XXNode node, boolean identity) {
		return children.indexOf(node);
	}

	public Vector2 parentToLocalCoordinates (Vector2 parentCoords) {
		final float rotation = getRotation();
		final float scaleX = getScaleX();
		final float scaleY = getScaleY();
		final float childX = getX();
		final float childY = getY();

		if (rotation == 0) {
			if (scaleX == 1 && scaleY == 1) {
				parentCoords.x -= childX;
				parentCoords.y -= childY;
			} else {
				final float originX = getAnchorPointX();
				final float originY = getAnchorPointY();
				if (originX == 0 && originY == 0) {
					parentCoords.x = (parentCoords.x - childX) / scaleX;
					parentCoords.y = (parentCoords.y - childY) / scaleY;
				} else {
					parentCoords.x = (parentCoords.x - childX - originX) / scaleX + originX;
					parentCoords.y = (parentCoords.y - childY - originY) / scaleY + originY;
				}
			}
		} else {
			final float cos = (float)Math.cos(rotation * MathUtils.degreesToRadians);
			final float sin = (float)Math.sin(rotation * MathUtils.degreesToRadians);

			final float originX = getAnchorPointX();
			final float originY = getAnchorPointY();

			if (scaleX == 1 && scaleY == 1) {
				if (originX == 0 && originY == 0) {
					float tox = parentCoords.x - childX;
					float toy = parentCoords.y - childY;

					parentCoords.x = tox * cos + toy * sin;
					parentCoords.y = tox * -sin + toy * cos;
				} else {
					final float worldOriginX = childX + originX;
					final float worldOriginY = childY + originY;
					final float fx = -originX;
					final float fy = -originY;

					final float x1 = cos * fx - sin * fy + worldOriginX;
					final float y1 = sin * fx + cos * fy + worldOriginY;

					final float tox = parentCoords.x - x1;
					final float toy = parentCoords.y - y1;

					parentCoords.x = tox * cos + toy * sin;
					parentCoords.y = tox * -sin + toy * cos;
				}
			} else {
				if (originX == 0 && originY == 0) {
					final float tox = parentCoords.x - childX;
					final float toy = parentCoords.y - childY;

					parentCoords.x = (tox * cos + toy * sin) / scaleX;
					parentCoords.y = (tox * -sin + toy * cos) / scaleY;
				} else {
					final float worldOriginX = childX + originX;
					final float worldOriginY = childY + originY;
					final float fx = -originX * scaleX;
					final float fy = -originY * scaleY;

					final float x1 = cos * fx - sin * fy + worldOriginX;
					final float y1 = sin * fx + cos * fy + worldOriginY;

					final float tox = parentCoords.x - x1;
					final float toy = parentCoords.y - y1;

					parentCoords.x = (tox * cos + toy * sin) / scaleX;
					parentCoords.y = (tox * -sin + toy * cos) / scaleY;
				}
			}
		}
		return parentCoords;
	}

	@Override
	public void dispose() {
		if(this.screen != null){
			this.screen.remove(this);
			this.screen = null ;
		}
		if(parent != null){
			parent.remove(this);
		}
		this.children.clear() ;
	}

	public float getDrawRotation(){
		if(parent != null){
			return parent.getRotation() + rotation;
		}
		return rotation ;
	}

	public boolean hasEventListener(XXEventType eventType) {
		for(XXNode node : children){
			if(node.hasEventListener(eventType)){
				return true ;
			}
		}
		return this.listeners[eventType.ordinal()] != null;
	}
	
	/**
	 * 是否重叠
	 * @return
	 */
	public boolean overlaps(XXNode node){
		return !(point.x > node.point.x + node.width || point.x + width < node.point.x || point.y > node.point.y + node.height || point.y + height < node.point.y);
	}
	
	public void alterPosition(float x , float y){
		point.x += x ;
		point.y += y ;
	}
	
	public boolean foucsAble(){
		return this.touchable  != XXTouchable.disabled ;
	}
	
	public void focusMe(){
		if(!foucsAble()){
			throw new RuntimeException("Can't be a focus");
		}
		XX.director.setFocus(this);
	}
	
	public void unFocus(){
		XX.director.lostFocus();
	}
	
	public List<XXNode> getChildren(){
		return new ArrayList<XXNode>(children);
	}
	
	public <T extends XXNode>XXNode addTween(XXBaseTween<?> tween){
		tweens.addLast(tween);
		XX.director.addTween(tween);
		return this ;
	}
	
	public BaseTween<?> getLastTween(){
		return tweens.getLast() ;
	}
	
	public List<XXBaseTween<?>> getTweens(){
		return new ArrayList<XXBaseTween<?>>(tweens) ;
	}
	
	public void freeAllTweens(){
		List<XXBaseTween<?>> allTweens = getTweens();
		for(XXBaseTween<?> xxTween : allTweens){
			xxTween.dispose() ;
		}
	}

	public void removeTween(XXTween tween) {
		this.tweens.remove(tween);
	}
	
	public void touchAble(){
		this.touchable = XXTouchable.enabled ;
	}
	
	public void touchChildrenAble(){
		this.touchable = XXTouchable.onlyChildren ;
	}
	
	public void unTouchAble(){
		this.touchable = XXTouchable.disabled ;
	}
	
	public boolean isTouchAble(){
		return this.touchable != XXTouchable.disabled  ;
	}
	
	public float getDrawWidth(){
		return getWidth() * getDrawScaleX() ;
	}
	
	public float getDrawHeight(){
		return getHeight() * getDrawScaleY() ;
	}
	
	public float getDrawAnchorPointX(){
		return getAnchorPointX() * getDrawScaleX() ;
	}
	
	public float getDrawAnchorPointY(){
		return getAnchorPointY() * getDrawScaleY() ;
	}
	
	public void clearChildren(){
		this.children.clear() ;
	}
	
	public void setDoubleClickEvent(XXTouchListener eventListener) {
		this.setListener(XXEventType.DOUBLE_CLICK, eventListener);
	}
	
	public void setLongTouchEvent(XXTouchListener eventListener) {
		this.setListener(XXEventType.LONG_TOUCH, eventListener);
	}
	
	public void setClickUpEvent(XXTouchListener eventListener){
		this.setListener(XXEventType.CLICK_UP, eventListener);
	}
	
	public void setClickDownEvent(XXTouchListener eventListener){
		this.setListener(XXEventType.CLICK_DOWN, eventListener);
	}
	
	public void setFocusOnEvent(XXFocusListener eventListener){
		this.setListener(XXEventType.FOUCS_ON, eventListener);
	}
	
	public void setLostFocusEvent(XXFocusListener eventListener){
		this.setListener(XXEventType.LOST_FOUCS, eventListener);
	}
	
	public void setKeyDownEvent(XXInputListener eventListener){
		this.setListener(XXEventType.KEYDOWN, eventListener);
	}
	
	public void setKeyUpEvent(XXInputListener eventListener){
		this.setListener(XXEventType.KEYUP, eventListener);
	}
	
	public void setKeyPressEvent(XXKeyPressListener eventListener){
		this.setListener(XXEventType.KEYPRESS, eventListener);
	}
	
	public void setTouchDraggedEvent(XXTouchDragListener eventListener){
		this.setListener(XXEventType.TOUCHDRAGGED, eventListener);
	}

	public boolean isCreated() {
		return created;
	}

	public void setCreated(boolean created) {
		this.created = created;
	}
	
}
